﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using CustomNetworking;
using System.Text.RegularExpressions;

namespace Boggle
{
    //event handler delegates
    public delegate void GameStartedHandler(object sender, ArrayEventArgs e);
    public delegate void GameStoppedHandler(object sender, ArrayEventArgs e);
    public delegate void GameEndedHandler(object sender, EventArgs e);
    public delegate void TimeChangedHandler(object sender, StringEventArgs e);
    public delegate void ScoreChangedHandler(object sender, ArrayEventArgs e);
    public delegate void TerminatedHandler(object sender, EventArgs e);
    public delegate void IgnoringHandler(object sender, StringEventArgs e);
    public delegate void ConnectedHandler(object sender, EventArgs e);
    public delegate void ConnectionFailedHandler(object sender, EventArgs e);
    public delegate void MessageSentHandler(object sender, StringEventArgs e);

    /// <summary>
    /// Handles 2-way communication with a Boggle server via a line-based protocol.
    /// 
    /// Each time a command is received from the server, an appropriate event is
    /// raised which signals the GUI controller to update the game.
    /// </summary>
    public class BoggleConnector
    {
        TcpClient client;
        StringSocket socket;        
        ManualResetEvent connectDone = new ManualResetEvent(false);

        public event GameStartedHandler GameStarted;
        public event GameEndedHandler ConnectionClosed;
        public event GameStoppedHandler GameStopped;
        public event TimeChangedHandler TimeChanged;
        public event ScoreChangedHandler ScoreChanged;
        public event TerminatedHandler Terminated;
        public event IgnoringHandler Ignored;
        public event MessageSentHandler MessageSent;
        public event ConnectedHandler Connected;
        public event ConnectionFailedHandler ConnectionFailed;

        // game state properties
        public int Score1 { get; private set; }
        public int Score2 { get; private set; }
        public int Time { get; private set; }
        public string Name { get; private set; }
        public string OpponentName { get; private set; }
        public string Board { get; private set; }
        
        /// <summary>
        /// Instantiates the connector but does not connect to the server.
        /// </summary>
        /// <param name="n">The name of the current player.</param>
        public BoggleConnector(string n)
        {
            Name = n;
            client = new TcpClient();
        }

        /// <summary>
        /// Tries to connect to the server passed as the string parameter
        /// on port 2000.
        /// </summary>
        /// <param name="s">The name of the server.</param>
        /// <returns>Returns true if the connection is successful.</returns>
        public bool ConnectToServer(string s)
        {
            // reset the MRE
            connectDone.Reset();

            client.BeginConnect(s, 2000, ConnectionAccepted, client);

            // wait for the connection to be established
            if (connectDone.WaitOne(3000))
            {
                // wrap the socket and start listening
                socket = new StringSocket(client.Client, new UTF8Encoding());
                socket.BeginReceive(ReceiveCallback, null);

                // send the 'PLAY' command
                string playMsg = "PLAY " + Name + "\n";
                socket.BeginSend(playMsg, SendCallback, playMsg);

                // fire the Connected event
                if (Connected != null)
                    Connected(this, null);                

                return true;
            }
            else
            {
                // fire the ConnectionFailed event
                if (ConnectionFailed != null)
                    ConnectionFailed(this, null);

                return false;
            }
        }

        /// <summary>
        /// Callback for ConnectToServer.  Used to determine
        /// if the connection has been established.
        /// </summary>
        private void ConnectionAccepted(IAsyncResult result)
        {
            // get a handle on the new socket
            TcpClient c = (TcpClient)result.AsyncState;

            // signal that the connection has been made
            if (c.Connected)
                connectDone.Set();
        }

        /// <summary>
        /// Closes the StringSocket.
        /// </summary>
        public void CloseConnection()
        {
            if (client.Connected)
                socket.CloseSocket(5);
        }

        /// <summary>
        /// Sends a string message to the server.
        /// </summary>
        /// <param name="s">The message to be sent to the server.</param>
        public void SendMessage(string s)
        {
            if (client.Connected)
                socket.BeginSend(s, SendCallback, null);
        }

        /// <summary>
        /// Callback for BeginSend; currently unused.
        /// </summary>
        private void SendCallback(Exception ex, object p)
        {
            if (MessageSent != null)
                MessageSent(this, new StringEventArgs((string)p));
        }

        /// <summary>
        /// Processes messages sent from the server and passes them
        /// to the appropriate method for firing the events.
        /// </summary>
        private void ReceiveCallback(String s, Exception ex, object p)
        {
            try
            {
                if (s != null)
                {
                    // process the message
                    if (s.Substring(0, 5) == "TIME ")
                        OnTime(s.Substring(5));

                    else if (s.Substring(0, 6) == "SCORE ")
                        OnScoreChanged(s.Substring(6));

                    else if (s.Substring(0,9) == "IGNORING ")
                        OnIgnoring(s.Substring(9));

                    else if (s.Substring(0, 6) == "START ")
                        OnStart(s.Substring(6));

                    else if (s.Substring(0, 5) == "STOP ")
                        OnStop(s.Substring(5));

                    else if (s == "TERMINATED")
                        OnTerminated();                    

                    // listen for the next message
                    socket.BeginReceive(ReceiveCallback, null);
                }
                else
                {
                    OnConnectionClosed();
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        #region Events
        protected void OnStart(string s)
        {
            // parse the start message
            string[] start = s.Split(null);

            Board = start[0];
            Time = Int32.Parse(start[1]);

            OpponentName = "";

            // if the opponent's name contained spaces,
            // concatenate the individual words
            for (int i = 2; i < start.Length; i++)
                OpponentName += start[i] + " ";

            // prepare the args array
            string[] args = new string[3];
            args[0] = start[0];
            args[1] = start[1];
            args[2] = OpponentName.Trim();

            if (GameStarted != null)
                GameStarted(this, new ArrayEventArgs(args));
            OpponentName = OpponentName.Trim();
        }

        protected void OnStop(string s)
        {
            // split the STOP message at each number
            string[] substrings = Regex.Split(s, @"[0-9]+");

            for (int i = 0; i < substrings.Count(); i++)
            {
                substrings[i] = substrings[i].Trim();
            }

            if (GameStopped != null)
                GameStopped(this, new ArrayEventArgs(substrings));            
        }

        protected void OnScoreChanged(string s)
        {
            // parse the scores
            string[] scores = s.Split(null);

            // update each player's score
            Score1 = Int32.Parse(scores[0]);
            Score2 = Int32.Parse(scores[1]);

            if (ScoreChanged != null)
                ScoreChanged(this, new ArrayEventArgs(scores));            
        }

        protected void OnTime(string s)
        {
            // update the remaining time
            Time = Int32.Parse(s);

            if (TimeChanged != null)
                TimeChanged(this, new StringEventArgs(s));            
        }

        protected void OnTerminated()
        {
            if (Terminated != null)
                Terminated(this, null);            
        }

        protected void OnIgnoring(string s)
        {
            if (Ignored != null)
                Ignored(this, new StringEventArgs(s));            
        }

        protected void OnConnectionClosed()
        {
            if (ConnectionClosed != null)
                ConnectionClosed(this, null);            
        }
        #endregion

    }
}
